package gui;

import items.*;

import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.HashSet;
import java.util.Set;

import be.kuleuven.cs.som.annotate.Model;

import roborally.*;

import elements.*;
import exceptions.IllegalPlacementException;
import exceptions.IllegalTerminatedException;
/**
 * @version	3.0
 * @author 	Lies Tambeur 	r0264469	2Bcwsbbh
 * @author	Eline Vanrykel	r0264543	2Bcwselt
 */
public class Facade implements IFacade<Board, Robot, Wall, Battery, RepairKit, SurpriseBox>{
	
	private static Set<Board> boards = new HashSet<Board>();
	
	/**
	 * @return	| if(!Board.canHaveAsHeight(height) || !Board.canHaveAsWidth(width))
	 * 			| 	then result == null
	 * 			| else
	 * 			|	result == new Board(width, height)
	 */
	@Override
	public Board createBoard(long width, long height) {
		if(!Board.canHaveAsHeight(height) || !Board.canHaveAsWidth(width)){
			return null;
		}
		Board theBoard = new Board(width, height);
		boards.add(theBoard);
		return theBoard;
	}
	
	/**
	 * @param board
	 * @post	| new.getBoards().contains(board)
	 */
	public void addBoard(Board board){
		if(board != null && !boards.contains(board)){
			boards.add(board);
		}
	}
	
	/**
	 * @param board
	 * @post	| !new.getBoards().contains(board)
	 */
	public static void removeBoard(Board board){
		if(board!=null && board.isTerminated() && boards.contains(board)){
			boards.remove(board);
		}
	}
	
	/**
	 * @return	| result == this.boards
	 */
	public static Set<Board> getBoards(){
		return boards;
	}
	
	/**
	 * @effect	| board1.merge(board2)
	 */
	@Override
	public void merge(Board board1, Board board2) {
		try{
			board1.merge(board2);
		}
		catch(NullPointerException e){ 
			System.err.println("Illegal board.");
		}
	}
	
	/**
	 * @return	| if(initialEnergy<0 || weight < 0 || initialEnergy>Battery.getMaxEnergy().toEnergyUnit(EnergyUnit.WATTSECONDS).getAmount() || weight>Battery.getMaxWeight())
	 * 			|	then result == null
	 * 			| else
	 * 			|	then result == new Battery(initialEnergy,weight)
	 */
	@Override
	public Battery createBattery(double initialEnergy, int weight) { 
		if(initialEnergy<0 || weight < 0){
			return null;
		}
		if(initialEnergy>Battery.getMaxEnergy().toEnergyUnit(EnergyUnit.WATTSECONDS).getAmount() || weight>Battery.getMaxWeight()){
			return null;
		}
		EnergyAmount energy = new EnergyAmount(initialEnergy, EnergyUnit.WATTSECONDS);
		return new Battery(energy,weight);
	}
	
	/**
	 * @effect	| 	this.put(board, x, y, battery)
	 */
	@Override
	public void putBattery(Board board, long x, long y, Battery battery) {
		this.put(board, x, y, battery);
	}
	
	/**
	 * @return	| result == battery.getPosition().getX()
	 */
	@Override
	public long getBatteryX(Battery battery) throws IllegalStateException { 
		return battery.getPosition().getX();
	}

	/**
	 * @return	| result == battery.getPosition().getY()
	 */
	@Override
	public long getBatteryY(Battery battery) throws IllegalStateException { 
		return battery.getPosition().getY();
	}
	
	/**
	 * @return	| for one value in Orientation.values():
	 * 			|	if(value.toInt() == orientation && initialEnergy < 0)
	 * 			|		then result == new Robot(value,initialEnergy)
	 * 			| else
	 * 			|	result == null
	 */
	@Override
	public Robot createRobot(int orientation, double initialEnergy) {
		Orientation orient = null;
		if(orientation == 0){
			orient = Orientation.UP;
		}
		else if(orientation == 1){
			orient = Orientation.RIGHT;
		}
		else if(orientation == 2){
			orient = Orientation.DOWN;
		}
		else if(orientation == 3){
			orient = Orientation.LEFT;
		}
		if(orient == null || initialEnergy < 0){
			return null;
		}
		else{
			Robot robot=new Robot(orient,new EnergyAmount(initialEnergy, EnergyUnit.WATTSECONDS)); 
			return robot;
		}
	}

	/**
	 * @effect	|	this.put(board, x, y, robot)
	 */
	@Override
	public void putRobot(Board board, long x, long y, Robot robot) {
		this.put(board, x, y, robot);
	}

	/**
	 * @return	| result == robot.getPosition().getX()
	 */
	@Override
	public long getRobotX(Robot robot) throws IllegalStateException {
		return robot.getPosition().getX();
	}
	
	/**
	 * @return	| result == robot.getPosition().getY()
	 */
	@Override
	public long getRobotY(Robot robot) throws IllegalStateException {
		return robot.getPosition().getY();
	}

	/**
	 * @return	| result == robot.getOrientation().toInt()
	 */
	@Override
	public int getOrientation(Robot robot) {
		if(robot.isTerminated()){
			System.err.println("Robot is terminated.");
		}
		return robot.getOrientation().toInt();
	}

	/**
	 * @return	| result == robot.getEnergy().toEnergyUnit(EnergyUnit.WATTSECONDS).getAmount()
	 */
	@Override
	public double getEnergy(Robot robot) {
		if(robot.isTerminated()){
			System.err.println("Robot is terminated.");
		}
		return robot.getEnergy().toEnergyUnit(EnergyUnit.WATTSECONDS).getAmount();
	}
	
	/**
	 * @effect	| if(robot.isLegalEnergy(robot.getEnergy()-robot.getEnergyForMove()))
	 * 			|	then robot.move()
	 */
	@Override
	public void move(Robot robot) {
		if(!robot.isLegalEnergy(robot.getEnergy().subtractAndReturn(robot.getEnergyForMove()))){
			System.err.println("Insufficient energy for move.");
		}
		else{
			try{
				robot.move();
			} catch(IllegalStateException e){
				System.err.println("Robot is not standing on a board.");
			} catch (IllegalPlacementException i){
				System.err.println("Robot can't be moved because it is moving to an illegal position.");
			} catch(IllegalTerminatedException t){
				System.err.println("Robot is terminated.");
			}
		}
	}
	
	/**
	 * @effect	| if(robot.isLegalEnergy(robot.getEnergy()-Robot.getEnergyForTurn()))
	 * 			|	then robot.turn(Direction.CLOCKWISE)
	 */
	@Override
	public void turn(Robot robot) {
		if(!robot.isLegalEnergy(robot.getEnergy().subtractAndReturn(Robot.getEnergyForTurn()))){
			System.err.println("Insufficient energy for turn.");
		}
		else{
			try{
				robot.turn(Direction.CLOCKWISE);
			}
			catch(IllegalTerminatedException e){
				System.err.println("Robot is terminated.");
			}
		}
	}

	/**
	 * @return	| for each item in robot.getItems:
	 * 			|	result.contains(item)
	 */
	public Set<Item> getPossessions(Robot robot) {
		if(robot.isTerminated()){
			System.err.println("Robot is terminated.");
		}
		return new HashSet<Item>(robot.getItems());
	}

	/**
	 * @return	| result == 1
	 */
	@Override
	public int isMinimalCostToReach17Plus() {
		return 1;
	}

	/**
	 * @return	| result == robot.minEnergyToReachInWattSeconds(new Position(x,y))
	 */
	@Override
	public double getMinimalCostToReach(Robot robot, long x, long y) {
		try{
			return robot.minEnergyToReachInWattSeconds(new Position(x,y));
		}
		catch (IllegalTerminatedException e) {
			System.err.println("Robot is terminated.");
			return -1;
		}
	}

	/**
	 * @return	| result == 1
	 */
	@Override
	public int isMoveNextTo18Plus() {
		return 1;
	}

	/**
	 * @effect	| robot.moveNextTo(other)
	 */
	@Override
	public void moveNextTo(Robot robot, Robot other) {
		try{
			robot.moveNextTo(other);
		}
		catch(IllegalStateException e){
			System.err.println(e.getMessage());
		} catch (IllegalTerminatedException t) {
			System.err.println("Robot is terminated.");
		}
	}

	/**
	 * @effect	| if( robot.getEnergy().isSmallerThan(Robot.getEnergyRequiredToShoot()) )
	 * 			|	then robot.shoot()
	 */
	@Override
	public void shoot(Robot robot) throws UnsupportedOperationException {
		if(robot.getEnergy().isSmallerThan(Robot.getEnergyRequiredToShoot())){
			System.err.println("Robot has insufficient energy for move.");
		}
		else try{
			robot.shoot();
		}
		catch(IllegalStateException e){
			System.err.println(e.getMessage());
		} catch(IllegalTerminatedException t){
			System.err.println("Robot is terminated.");
		}
	}

	/**
	 * @return	| result == new Wall()
	 */
	@Override
	public Wall createWall() throws UnsupportedOperationException {
		return new Wall();
	}

	/**
	 * @effect	|	this.put(board, x, y, wall)
	 */
	@Override
	public void putWall(Board board, long x, long y, Wall wall)
			throws UnsupportedOperationException {
		this.put(board, x, y, wall);
	}
	
	/**
	 * @return	| result == wall.getPosition().getX()
	 */
	@Override
	public long getWallX(Wall wall) throws IllegalStateException,
			UnsupportedOperationException {
		return wall.getPosition().getX();
	}
	
	/**
	 * @return	| result == wall.getPosition().getY()
	 */
	@Override
	public long getWallY(Wall wall) throws IllegalStateException,
			UnsupportedOperationException {
		return wall.getPosition().getY(); 
	}

	/**
	 * @return	| result == board.<Robot>getElementsOfType(Robot.class)
	 */
	@Override
	public Set<Robot> getRobots(Board board) {
		return board.<Robot>getElementsOfType(Robot.class);
	}

	/**
	 * @return	| result == board.<Battery>getElementsOfType(Battery.class)
	 */
	@Override
	public Set<Battery> getBatteries(Board board) {
		return board.<Battery>getElementsOfType(Battery.class);
	}

	/**
	 * @return	| result == board.<Wall>getElementsOfType(Wall.class)
	 */ 
	@Override
	public Set<Wall> getWalls(Board board) throws UnsupportedOperationException {
		return board.<Wall>getElementsOfType(Wall.class);
	}
	
	/**
	 * @return	| result == new RepairKit(weight, new EnergyAmount(repairAmount, EnergyUnit.WATTSECONDS))
	 */
	@Override
	public RepairKit createRepairKit(double repairAmount, int weight) {
		return new RepairKit(weight, new EnergyAmount(repairAmount, EnergyUnit.WATTSECONDS));
	}
	
	/**
	 * @effect	| this.put(board, x, y, repairKit)	 
	 */
	@Override
	public void putRepairKit(Board board, long x, long y, RepairKit repairKit) {
		this.put(board, x, y, repairKit);
	}
	
	/**
	 * @return	| result == repairKit.getPosition().getX()
	 */
	@Override
	public long getRepairKitX(RepairKit repairKit) throws IllegalStateException {
		return repairKit.getPosition().getX();
	}
	
	/**
	 * @return	| result == repairKit.getPosition().getY()
	 */
	@Override
	public long getRepairKitY(RepairKit repairKit) throws IllegalStateException {
		return repairKit.getPosition().getY();
	}
	
	/**
	 * @return	| result = new SurpriseBox(weight)
	 */
	@Override
	public SurpriseBox createSurpriseBox(int weight) {
		return new SurpriseBox(weight);
	}

	/**
	 * @effect	| this.put(board, x, y, surpriseBox)
	 */
	@Override
	public void putSurpriseBox(Board board, long x, long y,
			SurpriseBox surpriseBox) {
		this.put(board, x, y, surpriseBox);
	}
	
	/**
	 * @return	| result == surpriseBox.getPosition().getX()
	 */
	@Override
	public long getSurpriseBoxX(SurpriseBox surpriseBox)
			throws IllegalStateException {
		return surpriseBox.getPosition().getX();
	}

	/**
	 * @return	| result == surpriseBox.getPosition().getY()
	 */
	@Override
	public long getSurpriseBoxY(SurpriseBox surpriseBox)
			throws IllegalStateException {
		return surpriseBox.getPosition().getY();
	}

	/**
	 * @effect	| this.pickUp(robot, battery)
	 */
	@Override
	public void pickUpBattery(Robot robot, Battery battery) {
		this.pickUp(robot, battery);		
	}

	/**
	 * @effect	| this.use(robot, battery)
	 */
	@Override
	public void useBattery(Robot robot, Battery battery) {
		this.use(robot, battery);
	}

	/**
	 * @effect	| this.drop(robot, battery)
	 */
	@Override
	public void dropBattery(Robot robot, Battery battery) {
		this.drop(robot, battery);
		
	}

	/**
	 * @effect	| this.pickUp(robot, repairKit)
	 */
	@Override
	public void pickUpRepairKit(Robot robot, RepairKit repairKit) {
		this.pickUp(robot, repairKit);
		
	}

	/**
	 * @effect	| this.use(robot, repairKit)
	 */
	@Override
	public void useRepairKit(Robot robot, RepairKit repairKit) {
		this.use(robot, repairKit);
	}

	/**
	 * @effect	| this.drop(robot, repairKit)
	 */
	@Override
	public void dropRepairKit(Robot robot, RepairKit repairKit) {
		this.drop(robot, repairKit);
	}

	/**
	 * @effect	| this.pickUp(robot, surpriseBox)
	 */
	@Override
	public void pickUpSurpriseBox(Robot robot, SurpriseBox surpriseBox) {
		this.pickUp(robot, surpriseBox);
	}

	/**
	 * @effect	| this.use(robot, surpriseBox)
	 */
	@Override
	public void useSurpriseBox(Robot robot, SurpriseBox surpriseBox) {
		this.use(robot, surpriseBox);
	}

	/**
	 * @effect	| this.drop(robot, surpriseBox)
	 */
	@Override
	public void dropSurpriseBox(Robot robot, SurpriseBox surpriseBox) {
		this.drop(robot, surpriseBox);
	}

	/**
	 * @effect	| from.transferItems(to)
	 */
	@Override
	public void transferItems(Robot from, Robot to) {
		try{
			from.transferItems(to);
		}
		catch(IllegalStateException e){
			System.err.println("Robots should stand next to each other in order to transfer items.");
		}
		catch(IllegalTerminatedException t){
			System.err.println("One of the robots is terminated.");
		}
		
	}

	/**
	 * @effect	| result == board.<RepairKit>getElementsOfType(RepairKit.class)
	 */
	@Override
	public Set<RepairKit> getRepairKits(Board board) {
		return board.<RepairKit>getElementsOfType(RepairKit.class);
	}

	/**
	 * @effect	| board.<SurpriseBox>getElementsOfType(SurpriseBox.class)
	 */
	@Override
	public Set<SurpriseBox> getSurpriseBoxes(Board board) {
		return board.<SurpriseBox>getElementsOfType(SurpriseBox.class);
	}

	/**
	 * @pre		The path is given in Java-syntax. That is, \'s are given by \\'s in a string.
	 * @post	| robot.getSavedProgram().toString() == the contents of the file at the given path
	 */
	@SuppressWarnings("deprecation")
	@Override
	public int loadProgramFromFile(Robot robot, String path) {
		File file = new File(path);
	    FileInputStream fis = null;
	    BufferedInputStream bis = null;
	    DataInputStream dis = null;
	    StringBuilder text = new StringBuilder();

	    try {
	      fis = new FileInputStream(file);
	      bis = new BufferedInputStream(fis);
	      dis = new DataInputStream(bis);
	      while (dis.available() != 0) {
	        text.append(dis.readLine());
	      }
	      fis.close();
	      bis.close();
	      dis.close();
	      try{
	    	  robot.saveProg(text.toString());
	    	  System.err.println("Program saved successfully!");
	    	  return 0;
	      }catch(Exception e){
	    	  System.err.println("Illegal input!");
	    	  return -1;
	      }
	    } catch (FileNotFoundException e) {
	    	System.err.println("File not found!");
	      return -1;
	    } catch (IOException e) {
	      return -1;
	    }
	}
	
	/**
	 * @pre		The given path is the path to where a txt-file will be created, not the path to the txt-file itself.	
	 * @effect	| the contents of path/programOfRobot == robot.getSavedProgram().toString()
	 */
	@Override
	public int saveProgramToFile(Robot robot, String path) {
		File file = null;
		FileWriter filewriter = null;
	    Writer output = null;
	    try {
	    	file = new File(path+"\\programOfRobot.txt");
	    	filewriter = new FileWriter(file);
	    	output = new BufferedWriter(filewriter);
	    	output.write(robot.getSavedProgram().toString());
	    	output.close();
	    	System.err.println("Program saved successfully to file : "+path+"\\programOfRobot.txt");
	    	return 0;
	    }
	    catch (IOException e) {
	    	System.err.println("Program couldn't be saved!");
			return -1;
		}
		
	}

	/**
	 * @effect	| writer.write(robot.getSavedProgram().toString())
	 */
	@Override
	public void prettyPrintProgram(Robot robot, Writer writer) {
		try {
			writer.write(robot.getSavedProgram().toString());
		} catch (IOException e) {
			e.printStackTrace();
		} catch(NullPointerException n){
			System.err.println("Robot doesn't contain a program.");
		}
	}
	
	/**
	 * @effect	| n times robot.executeSingleStepOfProg()
	 */
	@Override
	public void stepn(Robot robot, int n) {
		for(int i = 0; i<n; i++){
			robot.executeSingleStepOfProg();
		}
	}
	
	/**
	 * @param robot
	 * @param item
	 * @effect	| robot.pickUp(item)
	 */
	@Model
	private void pickUp(Robot robot, Item item){
		try{
			robot.pickUp(item);
		}
		catch (IllegalStateException e) {
			System.err.println("The robot must share the same position with the "+item.getClass().toString().trim().toLowerCase().substring(12)+" in order to pick it up.");
		}
		catch (IllegalArgumentException a){
			System.err.println("The robot is not allowed to carry this item. ");
		}
		catch (IllegalTerminatedException t){
			System.err.println("Robot or" +item.getClass().toString().trim().toLowerCase().substring(12)+"is terminated.");
		}
	}
	
	/**
	 * @param 	robot
	 * @param 	item
	 * @effect	| robot.drop(item)
	 */
	@Model
	private void drop (Robot robot, Item item){
		try{
			robot.drop(item);
		}
		catch(IllegalStateException e){
			System.err.println("The robot must carry the "+item.getClass().toString().trim().toLowerCase().substring(12)+" before it can drop it.");
		}
		catch(IllegalTerminatedException t){
			System.err.println("Robot or "+item.getClass().toString().trim().toLowerCase().substring(12)+ " is terminated.");
		}
	}
	
	/**
	 * @param 	robot
	 * @param 	item
	 * @effect	| robot.use(item)
	 */
	@Model
	private void use (Robot robot, Item item){
		try{
			robot.use(item);
		}
		catch (IllegalStateException e) {
			System.err.println("The robot must carry the "+item.getClass().toString().trim().toLowerCase().substring(12)+" in order to use it.");
		}
		catch(IllegalTerminatedException t){
			System.err.println("Robot or "+item.getClass().toString().trim().toLowerCase().substring(12)+"is terminated.");
		}
	}
	
	/**
	 * @param 	board
	 * @param 	x
	 * @param 	y
	 * @param 	element
	 * @effect	| if(board != null && boards.contains(board) && element.getBoard()==null && board.isValidPosition(new Position(x,y), element) && !board.isTerminated() && !element.isTerminated())
	 * 			|	this.put(board, x, y, wall)
	 */
	@Model
	private void put(Board board, long x, long y, Element element) {
		if(board==null || !boards.contains(board)){
			System.err.println("Invalid board");
		}
		else if(element.getBoard()!=null){
			System.err.println("The "+element.getClass().toString().trim().toLowerCase()+" is already placed on a board");
		}
		else if(!board.isValidPosition(new Position(x,y), element)){
			System.err.println("Invalid position");
		}
		else if(board.isTerminated() || element.isTerminated()){
			System.err.println("Board or "+element.getClass().toString().trim().toLowerCase().substring(15)+" is terminated.");
		}
		else{
			board.putElement(new Position(x,y),element);
		}
	}
}